package com.ho1ho.springboot.framework.core.auth;

import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Service;

import com.ho1ho.springboot.framework.core.utils.Utils;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Michael Leo
 * @version 1.0 2017/05/22
 *
 *          We create SecurityService to provide current logged in user and auto login user after registering an
 *          account.
 */
@Service
@Slf4j
public class AuthSecurityServiceImpl implements AuthSecurityService {

	@Resource
	SessionRegistry sessionRegistry;

	@Resource
	private AuthenticationManager authenticationManager;

	@Resource
	private UserDetailsService userDetailsService;

	@Override
	public String getLoggedInUsername() {
		// log.debug("findLoggedInUsername()");
		return Utils.getLoggedInUserName();
	}

	@Override
	public User getLoggedInUser() {
		// log.debug("getLoggedInUser()");
		return Utils.getLoggedInUser();
	}

	// http://stackoverflow.com/a/5070099
	// http://stackoverflow.com/a/42604773
	@Override
	public boolean autoLogin(HttpServletRequest request, String username, String password) {
		// log.debug("autoLogin()");

		UserDetails userDetails = userDetailsService.loadUserByUsername(username);
		UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password,
				userDetails.getAuthorities());

		Authentication auth = null;
		try {
			auth = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

		if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
			SecurityContextHolder.getContext().setAuthentication(auth);
			// request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
			// SecurityContextHolder.getContext());
			log.info("Auto login {} successfully!", ((User) (auth.getPrincipal())).getUsername());
			return true;
		}

		return false;
	}

	/*
	 * Before calling this method, usually you should also call invalidateSession(User) method.
	 */
	@Override
	public void removeAuthenticationInformation() {
		SecurityContextHolder.getContext().setAuthentication(null);
		SecurityContextHolder.clearContext();
	}

	/**
	 * @param request
	 *            from which to obtain a HTTP session (cannot be null)
	 * @param response
	 *            not used (can be <code>null</code>)
	 */
	@Override
	public void logout(HttpServletRequest request, HttpServletResponse response) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
		logoutHandler.setInvalidateHttpSession(true); // Default value is true.
		logoutHandler.setClearAuthentication(true); // Default value is true.
		// The second and third parameter are not used in logout method yet and can be null.
		logoutHandler.logout(request, response, auth);
	}

	/*
	 * After calling this method, usually you should also call removeAuthenticationInformation() method.
	 */
	@Override
	public boolean invalidateSession(User user) {
		if (user == null) {
			return false;
		}
		List<Object> o = sessionRegistry.getAllPrincipals();
		for (Object principal : o) {
			if (principal instanceof User) {
				final User loggedUser = (User) principal;
				if (user.getUsername().equals(loggedUser.getUsername())) {
					List<SessionInformation> sessionsInfo = sessionRegistry.getAllSessions(principal, false);
					if (null != sessionsInfo && sessionsInfo.size() > 0) {
						for (SessionInformation sessionInformation : sessionsInfo) {
							sessionInformation.expireNow();
							return true;
						}
					}
				}
			}
		}
		return false;
	}
}